﻿#include "qtlottiewidget.h"

#include <QStyleOption>
#include <QPainter>
#include <QDir>
#include <QFileInfo>

QtLottieWidget::QtLottieWidget(QWidget *parent)
    : QWidget{parent}
{
    m_timeLine.setEasingCurve(QEasingCurve::Linear);
    connect(&m_timeLine, &QTimeLine::frameChanged,
        this, [this](int frame) {
        emit sigFrameChange(frame, m_timeLine.currentTime() * m_speed);
        render(frame);
        repaint();
    });
    connect(&m_timeLine, &QTimeLine::finished, this, [this]{
        if(m_curCount > 1) {
            --m_curCount;
            if(m_repeatMode == RM_Reverse)
                m_timeLine.toggleDirection();
            play();
        } else if(m_loopCount == 0) {
            if(m_repeatMode == RM_Reverse)
                m_timeLine.toggleDirection();
            play();
        } else {
            emit sigFinished();
        }
    });
}

QtLottieWidget::~QtLottieWidget()
{
    unload();
}

bool QtLottieWidget::loadFromFile(const QString &filePath, bool loadResource)
{
    QMutexLocker locker(&m_mutex);
    std::unique_ptr<rlottie::Animation> player;
    if(loadResource) {
        QFile jsonFile(filePath);
        if(!jsonFile.open(QFile::ReadOnly))
            return false;
        QByteArray data = jsonFile.readAll();
        QFileInfo info(filePath);
        QString resPath = info.absolutePath() + "/";
        resPath = QDir::fromNativeSeparators(resPath);
        player = rlottie::Animation::loadFromData(data.data(),
            info.absoluteFilePath().toLocal8Bit().data(), resPath.toLocal8Bit().data());
    } else {
        player = rlottie::Animation::loadFromFile(filePath.toLocal8Bit().data());
    }
    if(!player)
        return false;
    if(player->totalFrame() == 0) {
        player.reset();
        return false;
    }
    m_player = std::move(player);
    init();
    return true;
}

bool QtLottieWidget::loadFromData(const QString &jsonData, const QString &key, const QString &resourcePath)
{
    QMutexLocker locker(&m_mutex);
    m_player = rlottie::Animation::loadFromData(
        jsonData.toLocal8Bit().data(), key.toLocal8Bit().data(), resourcePath.toLocal8Bit().data());
    if(!m_player)
        return false;
    if(m_player->totalFrame() == 0) {
        m_player.reset();
        return false;
    }
    init();
    return true;
}

bool QtLottieWidget::isLoaded()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return false;
    return true;
}

void QtLottieWidget::setCurTime(float second)
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return;
    m_timeLine.setCurrentTime((int)(second * 1000));
    int index = m_timeLine.currentFrame();
    render(index);
}

float QtLottieWidget::getCurTime()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return 0;
    return (m_timeLine.currentTime() / 1000.0f);
}

void QtLottieWidget::setCurFrame(int frame)
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return;
    double second = frame * m_player->duration() / m_player->totalFrame();
    setCurTime(second);
}

bool QtLottieWidget::play()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return false;
    switch (m_timeLine.state()) {
    case QTimeLine::NotRunning:
        m_timeLine.start();
        return true;
    case QTimeLine::Paused:
        m_timeLine.resume();
        return true;
    case QTimeLine::Running:
        break;
    default:
        break;
    }
    return false;
}

bool QtLottieWidget::play(const QString &marker)
{
    QMutexLocker locker(&m_mutex);
    auto frame = findFrameAtMarker(marker.toLocal8Bit().data());
    m_timeLine.setStartFrame(std::get<0>(frame));
    if (std::get<1>(frame) != 0)
        m_timeLine.setEndFrame(std::get<1>(frame));
    return play();
}

bool QtLottieWidget::play(const QString &startMarker, const QString endMarker)
{
    auto stframe = findFrameAtMarker(startMarker.toLocal8Bit().data());
    auto edframe = findFrameAtMarker(endMarker.toLocal8Bit().data());
    m_timeLine.setStartFrame(std::get<0>(stframe));
    if (std::get<0>(edframe) != 0)
        m_timeLine.setEndFrame(std::get<0>(edframe));
    return play();
}

bool QtLottieWidget::resume()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return false;
    if(m_timeLine.state() == QTimeLine::Running)
        return false;
    if(m_timeLine.currentFrame() == m_timeLine.endFrame()) {
        m_timeLine.start();
    } else {
        m_timeLine.resume();
    }
    return true;
}

bool QtLottieWidget::pause()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return false;
    switch (m_timeLine.state()) {
    case QTimeLine::NotRunning:
        break;
    case QTimeLine::Paused:
        break;
    case QTimeLine::Running:
        m_timeLine.setPaused(true);
        return true;
    default:
        break;
    }
    return false;
}

bool QtLottieWidget::stop()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return false;
    switch (m_timeLine.state()) {
    case QTimeLine::NotRunning:
        break;
    case QTimeLine::Paused:
    case QTimeLine::Running:
        m_timeLine.stop();
        return true;
    default:
        break;
    }
    m_timeLine.setDirection(QTimeLine::Forward);
    return false;
}

void QtLottieWidget::setLoopCount(int count)
{
    QMutexLocker locker(&m_mutex);
    m_curCount = count;
    m_loopCount = count;
    m_timeLine.setDirection(QTimeLine::Forward);
}

void QtLottieWidget::setSpeed(float speed)
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return;
    m_timeLine.stop();
    m_timeLine.setDirection(QTimeLine::Forward);
    m_speed = speed;
    int64_t allTimeMS = (int64_t)(m_player->duration() * 1000 / speed);
    m_timeLine.setDuration(allTimeMS);
}

void QtLottieWidget::setRepeatMode(RepeatMode mode)
{
    QMutexLocker locker(&m_mutex);
    m_repeatMode = mode;
    if(mode == RM_Restart)
        m_timeLine.setDirection(QTimeLine::Forward);
}

double QtLottieWidget::getFrameRate()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return 0;
    return m_player->frameRate();
}

qint64 QtLottieWidget::getAllFrame()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return 0;
    return (qint64)m_player->totalFrame();
}

double QtLottieWidget::getAllTime()
{
    QMutexLocker locker(&m_mutex);
    if(!m_player)
        return 0;
    return m_player->duration();
}

QSize QtLottieWidget::getDefaultSize()
{
    QMutexLocker locker(&m_mutex);
    return m_defaultSize;
}

rlottie::Animation* QtLottieWidget::player()
{
    QMutexLocker locker(&m_mutex);
    return m_player.get();
}

QImage QtLottieWidget::getImage()
{
    QMutexLocker locker(&m_mutexImage);
    return m_curImage.copy();
}

QImage QtLottieWidget::getDefaultImage()
{
    QMutexLocker locker(&m_mutexImage);
    QImage img;
    if(!m_player)
        return img;
    img = QImage(m_defaultSize, QImage::Format_ARGB32);
    int index = m_timeLine.currentFrame();
    rlottie::Surface surface((uint32_t*)img.bits(),
        img.width(), img.height(), (img.width() << 2));
    m_player->renderSync(index, surface);
    return img;
}

void QtLottieWidget::unload()
{
    m_timeLine.stop();
    m_player.reset();
    m_loopCount = 1;
    m_curCount = 1;
    m_speed = 1.0f;
    m_pos = 0.0f;
    m_startMarker.clear();
    m_endMarker.clear();
    m_curImage = QImage(size(), QImage::Format_ARGB32);
    m_timeLine.setCurrentTime(0);
    m_timeLine.setFrameRange(0, 0);
}

QStringList QtLottieWidget::getAllMarkers()
{
    QStringList names;
    auto markerList = m_player->markers();
    for (auto& i : markerList) {
        std::string curName = std::get<0>(i);
        names.append(QString::fromStdString(curName));
    }
    return names;
}

QtLottieWidget::MarkerFrame QtLottieWidget::findFrameAtMarker(const std::string &markerName)
{
    if(!m_player)
        return std::make_tuple(0, 0);
    auto markerList = m_player->markers();
    auto marker = std::find_if(markerList.begin(), markerList.end()
        , [&markerName](const auto& e) {
            return std::get<0>(e) == markerName;
        });
    if (marker == markerList.end())
       return std::make_tuple(0, 0);
    return std::make_tuple(std::get<1>(*marker), std::get<2>(*marker));
}

void QtLottieWidget::resizeEvent(QResizeEvent*)
{
    QMutexLocker locker(&m_mutexImage);
    m_curImage = QImage(size(), QImage::Format_ARGB32);
    int index = m_timeLine.currentFrame();
    render(index);
}

void QtLottieWidget::paintEvent(QPaintEvent *e)
{
    QStyleOption op;
    op.initFrom(this);
    QPainter painter(this);
    style()->drawPrimitive( QStyle::PE_Widget, &op, &painter, this);

    QMutexLocker locker(&m_mutexImage);
    if(!m_curImage.isNull())
        painter.drawImage(0, 0, m_curImage);
    QWidget::paintEvent(e);
}

void QtLottieWidget::init()
{
    if(!m_player)
        return;
    size_t w, h;
    m_player->size(w, h);
    m_defaultSize.setWidth((int)w);
    m_defaultSize.setHeight((int)h);
    m_startMarker.clear();
    m_endMarker.clear();
    m_curImage = QImage(size(), QImage::Format_ARGB32);
    m_timeLine.setCurrentTime(0);
    int allFrame = (int)m_player->totalFrame();
    m_timeLine.setFrameRange(0, allFrame - 1);
    setSpeed(m_speed);
    render(0);
}
void QtLottieWidget::render(int frame)
{
    if(!m_player)
        return;
    QMutexLocker locker(&m_mutexImage);
    if(m_curImage.isNull())
        return;
    rlottie::Surface surface((uint32_t*)m_curImage.bits(),
        m_curImage.width(), m_curImage.height(), (m_curImage.width() << 2));
    m_player->renderSync(frame, surface);
}


